#!/bin/bash

set -e

echo "This will save a list of all bad blocks on a filesystem(device) to a file"
echo "which can later be fed to eg. fsck.ext4 via -l filename"
echo "READONLY test is done - no writing!"
echo "example usage: $0 /dev/mapper/ManjaroVG-ManjaroHome badblocks_home.list"
minimalexpectedblocksize=1024 #usually 4096(for ext4), but 1024 for ext2 /boot
USE_SUDO=1 #set to something if you wanna use sudo, else you'd have to be root, or see failures
#unset USE_SUDO  #to not use sudo

#16384*4096 bytes to read at once
blocksatonce=16384

#actually this(using env vars to hold 'sudo' name to execute) isn't working due to bash refusing to run empty cmd: "" but a workaround could be via aliasing sudo and shopt -s expand_aliases
#sudo='sudo' #leave empty if you don't want to use sudo, else 'sudo'
#sudo=
if [ -z "$USE_SUDO" ]; then
  alias sudo='' #use this if you don't want to use sudo
else
  alias sudo='sudo' #use this if you want sudo
fi

shopt -s expand_aliases
# ^ or else aliases will be ignored

# the -l param requires badblocks to have run with the right block size

if [ $# -gt 2 ]; then
  echo "too many parameters, maybe you forgot quotes?"
  echo "eg. $0 /dev/sda1 'long file name here.list'"
  exit 7
fi

#device name (something like /dev/sda1 or /dev/mapper/ManjaroVG-ManjaroHome )
device="$1"
if [ -z "$device" ]; then
  echo "you didn't specify a device"
  echo "eg. /dev/sda1 or /dev/mapper/ManjaroVG-ManjaroHome"
  exit 2
fi
badblocksfilename="$2"
if [ -z "$badblocksfilename" ]; then
  echo "you didn't specify an output filename to put the bad blocks list into"
  echo "eg. badblocks.list"
  exit 3
fi

if [ -e "$badblocksfilename" ] || [ -a "$badblocksfilename" ]; then
  echo "filename already exists '$badblocksfilename' refusing to overwrite just in case you might've used a device instead of a filename"
  exit 8
fi


#determine block size first (something like 4096)
blocksize=`sudo dumpe2fs "$device" | grep 'Block size'|awk '{ print $3 }'`

if [ -z "$blocksize" ]; then
  echo "something went wrong and \$blocksize was empty"
  exit 4
fi

#blocksize="abc"

re='^[0-9]+$'
if ! grep -E -q -- "$re" <<< "$blocksize" ; then
     echo "error: \$blocksize=$blocksize is not a number, for unknown reasons" >&2
     exit 5
fi

if [ "$blocksize" -lt $minimalexpectedblocksize ]; then #usually should be 4096 or more
  echo "unexpected \$blocksize=$blocksize is less than $minimalexpectedblocksize"
  exit 6
fi

echo "blocksize=$blocksize"

#-c 16384  blocks at a time (default 64)
#-s show progress
#-v verbose
#-o outputfile
#-- args end here
time sudo badblocks -b "$blocksize" -c "$blocksatonce" -sv -o "$badblocksfilename" -- "$device"
#takes 76mins 44sec on 1TB Samsung 840 EVO (non Pro) EXT0BB6Q  SSD
#takes about 6mins on 80G Intel X25-M Postville G2  SSD
#current badblocks on the whole device /dev/sda (80G intel above) for 1024 bytes blocks(default, see man badblocks) as a result of command sudo badblocks -sv -o badblocks.list -- /dev/sda  are:
# 33083868 33083869 33083870 33083871  which is exactly one 4096 bytes block
# I/O error, dev sda, sector 66167736
# current badblocks on /home device 4700535  (one 4096 bytes block) [takes 9 mins 12 sec to test with -c 16384 and -b 4096]
#hdparm reports bad sectors range: [66167736..66167743] that is two 4096 consecutive blocks
#smartctl -a /dev/sda  reports Error: UNC at LBA = 0x08f1a0c0 = 150053056
#XXX: note that writing the bad sector on SSD intel(above) worked! that is reading it afterwards worked.
# sudo hdparm --read-sector 66167736 -- /dev/sda  (this read FAILED: Input/output error)
# sudo hdparm --write-sector 66167736 -- /dev/sda   (deliberately ommiting the yes param)
#now this read works(sectors is zeroed):
# sudo hdparm --read-sector 66167736 -- /dev/sda
#this also worked(for the next sector):
# sudo hdparm --read-sector 66167737 -- /dev/sda  (this read FAILED: Input/output error)
# sudo hdparm --trim-sector-ranges 66167737:1 -- /dev/sda  (ommited the 'please destroy my drive' param lol)
# sudo hdparm --read-sector 66167737 -- /dev/sda  (worked and is zeroed)
#so, made all 8 sectors readable again by: sudo hdparm --trim-sector-ranges 66167736:8 -- /dev/sda
#now badblocks reports nothing bad
#and sudo smartctl -t select,66167000+1000 /dev/sda   reports nothing bad(anymore) when looking with sudo smartctl --all /dev/sda|less

echo "When the filesystem $device isn't mounted then you can run this command:"
echo "sudo fsck.ext4 -l $badblocksfilename -- $device"
echo "to add those bad blocks to the filesystem"

